package com.packer;

import android.annotation.SuppressLint;

import com.packer.walle.ApkV1Utils;
import com.packer.walle.ApkV2Utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Set;

public class PackerManager{

    public static boolean SHOW_LOG;

    static{
        try{
            @SuppressLint("PrivateApi")
            Class<?> SystemProperties = Class.forName("android.os.SystemProperties");
            Method get = SystemProperties.getMethod("get", String.class, String.class);
            String result = (String) get.invoke(SystemProperties, "debug.xlog.level", "0");
            SHOW_LOG = Integer.parseInt(result) > 99;
        }catch(NumberFormatException | NullPointerException ignored){
            SHOW_LOG = false;
        }catch(Exception ignored){
            SHOW_LOG = true;
        }
    }

    private PackerManager(){
    }

    /**
     * 读V1的渠道信息
     */
    public static String readChannelV1(File targetFile, String magic){
        if(SHOW_LOG){
            System.out.println("XLog.readChannelV1");
        }
        byte[] bytes = ApkV1Utils.readZipCommentByMagic(targetFile, magic);
        if(bytes != null && bytes.length > 0){
            return new String(bytes);
        }
        return "";
    }

    /**
     * 写V1的渠道信息
     *
     * @param context     Android环境的上下文，纯Java环境传null即可
     * @param targetFile  目标文件
     * @param magicStr    魔数
     * @param channelFile 传入null，android会取本身apk，也可传入已经写了渠道信息的APK
     */
    public static void writeChannelV1(Object context, File targetFile, String magicStr, File channelFile) throws IOException{
        if(SHOW_LOG){
            System.out.println("XLog.writeChannelV1");
        }
        if(!targetFile.exists()){
            throw new IOException("invalid src apk file!");
        }
        if(isBlank(magicStr)){
            throw new IOException("magicStr is empty");
        }

        byte[] commentBytes;
        if(channelFile == null){
            String sourceDir;
            try{
                sourceDir = getSourceDir(context);
            }catch(Throwable throwable){
                throw new IOException("getSourceDir error !");
            }
            File file = new File(sourceDir);
            commentBytes = ApkV1Utils.readZipCommentByMagic(file, magicStr);
        }else{
            commentBytes = ApkV1Utils.readZipCommentByMagic(channelFile, magicStr);
        }
        if(commentBytes == null){
            throw new IOException("channelFile not exist comment!");
        }
        ApkV1Utils.writeComment(targetFile, magicStr, commentBytes);
    }

    /**
     * 写V1的渠道信息
     *
     * @param targetApk   目标文件
     * @param magicStr    魔数
     * @param channelInfo 传入渠道信息字符串
     */
    public static void writeChannelV1(File targetApk, String magicStr, String channelInfo) throws IOException{
        if(SHOW_LOG){
            System.out.println("XLog.writeChannelV1");
        }
        if(!targetApk.exists()){
            throw new IOException("invalid src apk file!");
        }
        if(isBlank(magicStr)){
            throw new IOException("magicStr is empty");
        }
        ApkV1Utils.writeComment(targetApk, magicStr, channelInfo.getBytes());
    }

    /**
     * 读V2的渠道信息
     */
    public static String readChannelV2(File file, int blockId){
        if(SHOW_LOG){
            System.out.println("XLog.readChannelV2");
        }
        ByteBuffer buffer = ApkV2Utils.readBlockById(file, blockId);
        return ApkV2Utils.byteBufferToString(buffer);
    }

    /**
     * 写V2的渠道信息
     *
     * @param apkFile   目标文件
     * @param newBlocks 要写入的id，ByteBuffer
     * @throws IOException
     */
    public static void writeChannelV2(File apkFile, final LinkedHashMap<Integer, ByteBuffer> newBlocks) throws IOException{
        if(SHOW_LOG){
            System.out.println("XLog.writeChannelV2");
        }
        if(apkFile == null || !apkFile.exists()){
            throw new IOException("invalid apkFile!");
        }
        if(newBlocks == null || newBlocks.size() == 0){
            throw new IOException("invalid newBlocks!");
        }
        ApkV2Utils.ApkSigningBlockHandler apkSigningBlockHandler = new ApkV2Utils.ApkSigningBlockHandler(){
            @Override
            public LinkedHashMap<Integer, ByteBuffer> handleBlocks(LinkedHashMap<Integer, ByteBuffer> originIdValues){

                Set<Integer> keySet = newBlocks.keySet();
                for(Integer id : keySet){
                    if(id == 1896449818){ /* 签名区域数据不能被覆盖 */
                        continue;
                    }
                    originIdValues.put(id, newBlocks.get(id));
                }
                return originIdValues;
            }
        };
        ApkV2Utils.writeApkSigningBlock(apkFile, apkSigningBlockHandler);
    }

    /**
     * 写V2的渠道信息
     *
     * @param context    Android环境的上下文，纯Java环境传null即可
     * @param apkFile    目标文件
     * @param channelApk Android环境传入null，纯Java环境传入已经写了渠道信息的APK
     * @throws IOException
     */
    public static void writeChannelV2(Object context, File apkFile, final ArrayList<Integer> ids, File channelApk) throws IOException{
        if(SHOW_LOG){
            System.out.println("XLog.writeChannelV2");
        }
        if(apkFile == null || !apkFile.exists()){
            throw new IOException("invalid target apk file!");
        }

        if(channelApk == null){
            try{
                channelApk = new File(getSourceDir(context));
            }catch(Throwable ignored){
                throw new IOException("getSourceDir error !");
            }
        }

        final LinkedHashMap<Integer, ByteBuffer> newBlocks = ApkV2Utils.readBlockMap(channelApk);
        if(newBlocks == null || newBlocks.size() == 0){
            throw new IOException("invalid map list!");
        }
        ApkV2Utils.ApkSigningBlockHandler apkSigningBlockHandler = new ApkV2Utils.ApkSigningBlockHandler(){
            @Override
            public LinkedHashMap<Integer, ByteBuffer> handleBlocks(LinkedHashMap<Integer, ByteBuffer> originIdValues){


                if(SHOW_LOG){
                    System.out.println("XLog.writeBlocks.old->" + originIdValues.entrySet());
                }
                if(ids == null || ids.size() == 0){
                    Set<Integer> keySet = newBlocks.keySet();
                    for(Integer id : keySet){
                        if(id == 1896449818){ /* 签名区域数据不能被覆盖 */
                            continue;
                        }
                        originIdValues.put(id, newBlocks.get(id));
                    }
                }else{
                    for(Integer id : ids){
                        ByteBuffer value = newBlocks.get(id);
                        if(value == null || id == 1896449818){/* 签名区域数据不能被覆盖 */
                            continue;
                        }
                        originIdValues.put(id, value);
                    }
                }
                if(SHOW_LOG){
                    System.out.println("XLog.writeBlocks.new->" + originIdValues.entrySet());
                }
                return originIdValues;
            }
        };

        ApkV2Utils.writeApkSigningBlock(apkFile, apkSigningBlockHandler);
    }

    public static void copyFile(File srcFile, File destFile) throws IOException{
        if(srcFile == null){
            throw new IOException("copyFile，srcFile == null");
        }else if(!srcFile.exists()){
            throw new IOException("copyFile，srcFile is not exists");
        }
        if(destFile == null){
            throw new IOException("copyFile，destFile == null");
        }
        if(destFile.getAbsolutePath().equalsIgnoreCase(srcFile.getAbsolutePath())){
            throw new IOException("copyFile，srcFile path equals destFile path");
        }
        try(FileChannel source = new FileInputStream(srcFile).getChannel(); FileChannel destination = new FileOutputStream(destFile).getChannel()){
            destination.transferFrom(source, 0, source.size());
        }
    }

    // for android code
    public static String getSourceDir(Object context) throws Throwable{
        Class<?> contextClass = Class.forName("android.content.Context");
        Class<?> applicationInfoClass = Class.forName("android.content.pm.ApplicationInfo");
        Method getApplicationInfoMethod = contextClass.getMethod("getApplicationInfo");
        getApplicationInfoMethod.setAccessible(true);
        Object appInfo = getApplicationInfoMethod.invoke(context);
        // try ApplicationInfo.sourceDir
        Field sourceDirField = applicationInfoClass.getField("sourceDir");
        sourceDirField.setAccessible(true);
        String sourceDir = (String) sourceDirField.get(appInfo);
        if(sourceDir == null){
            // try ApplicationInfo.publicSourceDir
            sourceDirField = applicationInfoClass.getField("publicSourceDir");
            sourceDirField.setAccessible(true);
            sourceDir = (String) sourceDirField.get(appInfo);
        }
        if(sourceDir == null){
            // try Context.getPackageCodePath()
            Method getPackageCodePathMethod = contextClass.getMethod("getPackageCodePath");
            getPackageCodePathMethod.setAccessible(true);
            sourceDir = (String) getPackageCodePathMethod.invoke(context);
        }
        return sourceDir;
    }

    public static boolean isBlank(CharSequence str){
        return str == null || str.length() == 0 || str.toString().trim().length() == 0;
    }
}